fix(gemini): apply timeout after client_params merge and handle HttpOptions object#7629
Conversation
…ptions object
Two bugs in Gemini.get_client() caused self.timeout to be silently
ignored:
1. Ordering bug: timeout was injected into http_options BEFORE
client_params.update(self.client_params) was called. Any http_options
key inside client_params therefore overwrote the injected timeout.
2. Type bug: the isinstance(http_options, dict) guard silently skipped
timeout injection when the user passed an HttpOptions object (e.g.
HttpOptions(some_option='value')) inside client_params. The object
branch was simply not handled.
Fix:
- Move the timeout injection block AFTER client_params.update() so
client_params are fully merged before the timeout is applied.
- Normalise http_options to a plain dict before merging: call
model_dump(exclude_none=True) for pydantic/HttpOptions objects and
fall back to {} for any other non-dict type.
- Only inject self.timeout when the caller has NOT already set a timeout
key, preserving explicit user overrides.
Fixes agno-agi#7599
VANDRANKI
left a comment
There was a problem hiding this comment.
Two bugs fixed correctly in a single PR.
Bug 1 (order): client_params.update() was called after the timeout was injected, so any http_options key inside client_params would overwrite the just-set timeout. Moving the update() call before the timeout injection is the right fix.
Bug 2 (type): isinstance(http_options, dict) returned False for HttpOptions objects, silently skipping the timeout injection entirely. Normalizing via model_dump(exclude_none=True) is the right approach.
if 'timeout' not in http_options is a good addition - if the caller explicitly set a timeout in their http_options, this PR correctly preserves it rather than overwriting it. The deleted test test_timeout_does_not_override_client_params_http_options was testing wrong behavior; its replacement test_explicit_timeout_in_client_params_http_options_takes_precedence tests the correct behavior.
Minor: hasattr(http_options, 'model_dump') is duck typing that would match any object with a model_dump method. The more precise check would be isinstance(http_options, BaseModel) (importing from pydantic), or even isinstance(http_options, HttpOptions) if google.genai.types.HttpOptions is importable here. That said, duck typing is a reasonable tradeoff if the import adds overhead.
Also minor: the elif not isinstance(http_options, dict): http_options = {} branch silently drops any unrecognized http_options object. A log_warning here would help users debug unexpected configurations.
The three regression tests directly pin both bugs. LGTM.
|
Thanks for the review @VANDRANKI — addressed the minor feedback: log_warning added (e712285): The elif not isinstance(http_options, dict) fallback now logs a warning with the type name before falling back to an empty dict, so users can debug unexpected configurations. Re: duck typing — keeping hasattr(http_options, 'model_dump') as-is. While BaseModel is already imported, HttpOptions inherits from google.genai._common.BaseModel (which itself subclasses pydantic's BaseModel), so isinstance would work today. But the duck-typed check is more resilient to future google-genai refactors where the class hierarchy might change. As you noted, reasonable tradeoff. |
|
The On the duck-typing rationale: agreed. LGTM. |
|
Hi @ashpreetbedi — this fix has community LGTM, tests passing, and addresses the timeout issue in #7599. Would appreciate a review when you get a chance! |
VANDRANKI
left a comment
There was a problem hiding this comment.
The fix correctly addresses both sub-bugs. A few observations.
Bug 1 (ordering): correct fix
Moving before the timeout injection is the right approach. The old order let clobber the injected entirely.
Bug 2 (HttpOptions object): correct fix
to detect a Pydantic model and then to flatten it into a plain dict before merging is clean. One edge case: if is not a Pydantic model in a future SDK version but still has a method with different semantics, this could break. A more defensive check would be with , but the current duck-typing approach is pragmatic and fine for now.
Deleted test concern
The removed test would still pass under the new logic: when supplies , after /dict-check the dict already contains , so the guard correctly skips injection and the effective timeout remains 60000 ms. The test was deleted unnecessarily. Restoring it documents the caller-explicit-timeout-wins precedence rule. Not blocking, but worth adding back as a positive assertion.
New test coverage
and together cover the two regression scenarios directly. Good.
LGTM. Ready to merge once the deleted test is optionally restored.
…tp_options Restores the deleted test as suggested by reviewer. The test still passes under the new logic and documents the caller-explicit-timeout-wins precedence rule as a positive assertion.
|
Thanks @VANDRANKI — restored test_timeout_does_not_override_client_params_http_options ( |
Description
Fixes two bugs in
Gemini.get_client()that causedself.timeoutto be silently ignored whenclient_paramsis provided.Fixes #7599
Root Cause
Bug 1 — Wrong order: timeout was applied before
client_params.update()Any
http_optionskey insideclient_paramswould overwrite the injected timeout, causing requests to fall back to the underlying client default (e.g. 120 seconds) regardless of what the user set.Bug 2 — Type guard:
HttpOptionsobject silently skippedWhen a user passes
http_options=HttpOptions(some_option="value")insideclient_params, theisinstance(dict)guard evaluates toFalseand timeout injection is silently skipped with no warning or error.Fix
Changes:
client_params.update()so all user params are fully merged first.http_optionsto a plain dict viamodel_dump(exclude_none=True)when it is anHttpOptions/ pydantic object, preserving any other options the user set.self.timeoutwhen the caller has not already set atimeoutkey, so explicit user overrides are respected.Tests
Added 3 regression tests to the existing
TestGeminiTimeoutclass intests/unit/models/google/test_gemini.py:test_timeout_survives_client_params_updateclient_paramscarries unrelated keystest_timeout_with_http_options_object_in_client_paramsHttpOptions-like object is normalized; timeout is injected and existing options preservedtest_explicit_timeout_in_client_params_http_options_takes_precedencetimeoutinsideclient_paramshttp_options is not overwrittenAll 7 tests in
TestGeminiTimeoutpass.Type of Change